#include "capture.h"
#include "demo_log.h"

void V4l2SearchFormat(int fd, unsigned int *fmt)
{
    int i, j, k;
    struct v4l2_fmtdesc enumFmtDesc = {};
    struct v4l2_frmsizeenum enumFrmSize = {};
    struct v4l2_frmivalenum  enumFraMival = {};
    unsigned int fmtMax = 50;

    for (i = 0; i < fmtMax; i++) {
        enumFmtDesc.index = i;
        enumFmtDesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (ioctl(fd, VIDIOC_ENUM_FMT, &enumFmtDesc) < 0) {
            break;
        }

        for (j = 0; j < fmtMax; j++) {
            enumFrmSize.index = j;
            enumFrmSize.pixel_format = enumFmtDesc.pixelformat;
            if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &enumFrmSize) < 0) {
                break;
            }

            for (k = 0; k < fmtMax; k++) {
                enumFraMival.index = k;
                enumFraMival.pixel_format = enumFrmSize.pixel_format;
                enumFraMival.width = enumFrmSize.discrete.width;
                enumFraMival.height = enumFrmSize.discrete.height;
                if (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &enumFraMival) < 0) {
                    break;
                }

                demo_debug("desc = %s", enumFmtDesc.description);
                demo_debug("pixelformat = 0x%X", enumFmtDesc.pixelformat);
                demo_debug("width = %d, height = %d", enumFrmSize.discrete.width, enumFrmSize.discrete.height);
                demo_debug("numerator = %d, denominator = %d\n", enumFraMival.discrete.numerator, enumFraMival.discrete.denominator);

                *fmt = SET_PIXEL_FORMAT(enumFrmSize.pixel_format, *fmt);
            }
        }
    }
}

int V4L2SetBufType(int fd, unsigned int *bufType)
{
    struct v4l2_capability cap = {};
    *bufType = 0;

    int ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
    if (ret < 0) {
        demo_err("VIDIOC_QUERYCAP failed");
        return -1;
    }

    if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
        demo_err("capabilities is not support V4L2_CAP_STREAMING");
        return -2;
    }

    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
        demo_err("capabilities is not support V4L2_CAP_VIDEO_CAPTURE");
        return -3;
    }

    *bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    return 0;

}

int V4L2GetFmt(int fd, struct DeviceFormat *deviceFormat)
{
    int ret;
    struct v4l2_format fmt = {};

    if (deviceFormat->bufType == 0) {
        V4L2SetBufType(fd, &deviceFormat->bufType);
        if (deviceFormat->bufType == 0) {
            demo_err("set buf type failed");
            return -1;
        }
    }

    fmt.type = deviceFormat->bufType;

    ret = ioctl(fd, VIDIOC_G_FMT, &fmt);
    if (ret < 0) {
        demo_err("ioctl VIDIOC_G_FMT failed");
        return ret;
    }

    if (fmt.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
        deviceFormat->fmtDesc.pixelformat = fmt.fmt.pix.pixelformat;
        deviceFormat->fmtDesc.width = fmt.fmt.pix.width;
        deviceFormat->fmtDesc.height = fmt.fmt.pix.height;
        deviceFormat->fmtDesc.sizeimage = fmt.fmt.pix.sizeimage;
    } else {
        demo_err("current does not support other type");
        return -1;
    }

    demo_debug("deviceFormat->fmtDesc.pixelformat = 0x%X", deviceFormat->fmtDesc.pixelformat);
    demo_debug("deviceFormat->fmtDesc.width = %d", deviceFormat->fmtDesc.width);
    demo_debug("deviceFormat->fmtDesc.height = %d", deviceFormat->fmtDesc.height);
    demo_debug("deviceFormat->fmtDesc.sizeimage = %d", deviceFormat->fmtDesc.sizeimage);

    return 0;
}

int V4L2SetFmt(int fd, struct DeviceFormat *deviceFormat)
{
    int ret;
    struct v4l2_format fmt = {};

    if (deviceFormat->bufType == 0) {
        V4L2SetBufType(fd, &deviceFormat->bufType);
        if (deviceFormat->bufType == 0) {
            demo_err("set buf type failed");
            return -1;
        }
    }

    fmt.type = deviceFormat->bufType;

    if (fmt.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
        fmt.fmt.pix.pixelformat = deviceFormat->fmtDesc.pixelformat;
        fmt.fmt.pix.width = deviceFormat->fmtDesc.width;
        fmt.fmt.pix.height = deviceFormat->fmtDesc.height;
    } else {
        demo_err("current does not support other type");
        return -1;
    }

    ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
    if (ret < 0) {
        demo_err("ioctl VIDIOC_S_FMT failed");
        return ret;
    }

    ret = V4L2GetFmt(fd, deviceFormat);
    if (ret < 0) {
        demo_err("get fmt failed");
        return ret;
    }

    return 0;
}

int V4L2ReqBuffers(int fd, int count, struct DeviceFormat *deviceFormat)
{
    int ret;
    struct v4l2_requestbuffers req = {};

    req.count = count;
    req.type = deviceFormat->bufType;
    req.memory = deviceFormat->memDesc.memoryType;
    deviceFormat->memDesc.memoryList = (struct MemoryList *)calloc(req.count, sizeof(struct MemoryList));
    if (deviceFormat->memDesc.memoryList == NULL) {
        demo_err("calloc buf failed");
        return -1;
    }

    ret = ioctl(fd, VIDIOC_REQBUFS, &req);
    if (ret < 0) {
        demo_err("req buf failed");
        return ret;
    }

    if (req.count != count) {
        req.count = 0;
        req.type = deviceFormat->bufType;
        req.memory = deviceFormat->memDesc.memoryType;
        ret = ioctl(fd, VIDIOC_REQBUFS, &req);
        if (ret < 0) {
            demo_err("req buf failed");
        }

        return -1;
    }

    deviceFormat->memDesc.memCount = req.count;
    demo_debug("req buf success, count = %d", deviceFormat->memDesc.memCount);

    return 0;
}

int V4L2MmapBuffer(int fd, int index, struct DeviceFormat *deviceFormat)
{
    int ret;
    struct v4l2_buffer buf = {
        .type = deviceFormat->bufType,
        .memory = deviceFormat->memDesc.memoryType,
        .index = index,
    };

    if (buf.type != V4L2_BUF_TYPE_VIDEO_CAPTURE || buf.memory != V4L2_MEMORY_MMAP) {
        demo_err("current does not support other type");
        return -1;
    }

    ret = ioctl(fd, VIDIOC_QUERYBUF, &buf);
    if ( ret < 0) {
        demo_err("querybuf failed");
        return ret;
    }

    deviceFormat->memDesc.memoryList[buf.index].length = buf.length;
    deviceFormat->memDesc.memoryList[buf.index].virtAddr = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
    if (deviceFormat->memDesc.memoryList[buf.index].virtAddr == NULL || deviceFormat->memDesc.memoryList[buf.index].length == 0) {
        demo_err("set buff addr or length failed");
        return -2;
    }
    deviceFormat->memDesc.memoryList[buf.index].saveImageAddr = calloc(1, buf.length);
    if (deviceFormat->memDesc.memoryList[buf.index].saveImageAddr == NULL) {
        demo_err("calloc image buf failed");
        return -3;
    }

    demo_debug("index = %d, length = %ld, offset = 0x%X", index, deviceFormat->memDesc.memoryList[buf.index].length, buf.m.offset);

    return 0;
}

int V4L2QueueBuffer(int fd, int index, struct DeviceFormat *deviceFormat)
{
    int ret;
    struct v4l2_buffer buf = {
        .type = deviceFormat->bufType,
        .memory = deviceFormat->memDesc.memoryType,
        .index = index,
    };

    if (buf.type != V4L2_BUF_TYPE_VIDEO_CAPTURE || buf.memory != V4L2_MEMORY_MMAP) {
        demo_err("current does not support other type");
        return -1;
    }

    ret = ioctl(fd, VIDIOC_QBUF, &buf);
    if (ret < 0) {
        demo_err("qbuf failed");
        return ret;
    }

    return 0;
}

int V4L2DequeueBuffer(int fd, int *index, struct DeviceFormat *deviceFormat)
{
    int ret;
    struct v4l2_buffer buf = {
        .type = deviceFormat->bufType,
        .memory = deviceFormat->memDesc.memoryType,
    };

    if (buf.type != V4L2_BUF_TYPE_VIDEO_CAPTURE || buf.memory != V4L2_MEMORY_MMAP) {
        demo_err("current does not support other type");
        return -1;
    }

    ret = ioctl(fd, VIDIOC_DQBUF, &buf);
    if (ret < 0) {
        demo_err("dqbuf failed");
        return ret;
    }

    *index = buf.index;
    memcpy(deviceFormat->memDesc.memoryList[buf.index].saveImageAddr, deviceFormat->memDesc.memoryList[buf.index].virtAddr, deviceFormat->memDesc.memoryList[buf.index].length);
    demo_debug("index = %d, length = %ld", *index, deviceFormat->memDesc.memoryList[buf.index].length);

    return 0;
}

int V4L2StreamOn(int fd, enum v4l2_buf_type type)
{
    
    int ret;
    enum v4l2_buf_type buf_type = type;

    if (buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
        demo_err("current does not support this type");
        return -1;
    }

    ret = ioctl(fd, VIDIOC_STREAMON, &buf_type);
    if (ret < 0) {
        demo_err("stream on failed");
        return ret;
    }

    return 0;
}

int V4L2StreamOff(int fd, enum v4l2_buf_type type)
{    
    int ret;
    enum v4l2_buf_type buf_type = type;

    if (buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
        demo_err("current does not support this type");
        return -1;
    }

    ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);
    if (ret < 0) {
        demo_err("stream on failed");
        return ret;
    }

    return 0;

}

void V4L2FreeBuf(struct DeviceFormat *deviceFormat)
{
    int i;
    for (i = 0; i < deviceFormat->memDesc.memCount; i++) {
        munmap(deviceFormat->memDesc.memoryList[i].virtAddr, deviceFormat->memDesc.memoryList[i].length);
        if (deviceFormat->memDesc.memoryList[i].saveImageAddr)
            free(deviceFormat->memDesc.memoryList[i].saveImageAddr);
    }

    if (deviceFormat->memDesc.memoryList)
        free(deviceFormat->memDesc.memoryList);
}

int Capture(const char *dev_name, struct DeviceFormat *deviceFormat, CallBack cb)
{
    int index;
    int ret;
    int i;

    // open camera
    int fd = open(dev_name, O_RDWR);
    if (fd < 0) {
        demo_err("open camera failed, fd = %d", fd);
        return -1;
    }

    // enum fmt
    deviceFormat->fmtDesc.pixelformat = 0;
    V4l2SearchFormat(fd, &deviceFormat->fmtDesc.pixelformat);
    demo_debug("pixelformat = 0x%X\n", deviceFormat->fmtDesc.pixelformat);

    // get cap
    ret = V4L2SetBufType(fd, &deviceFormat->bufType);
    if (ret < 0) {
        demo_err("set buf type failed, ret = %d", ret);
        return ret;
    }

    // set format
    ret = V4L2SetFmt(fd, deviceFormat);
    if (ret < 0) {
        demo_err("set fmt failed, ret = %d", ret);
        return ret;
    }

    // alloc buf
    ret = V4L2ReqBuffers(fd, V4L2_BUF_CNT, deviceFormat);
    if (ret < 0) {
        demo_err("req buffers failed, ret = %d", ret);
        return ret;
    }

    // mmap buf
    for (i = 0; i < deviceFormat->memDesc.memCount; i++) {
        ret = V4L2MmapBuffer(fd, i, deviceFormat);
        if (ret < 0) {
            demo_err("req buffers failed, ret = %d", ret);
            return ret;
        }

        ret = V4L2QueueBuffer(fd, i, deviceFormat);
        if (ret < 0) {
            demo_err("qbuf failed, ret = %d", ret);
            return ret;
        }
    }

    // start stream on
    ret = V4L2StreamOn(fd, deviceFormat->bufType);
    if (ret < 0) {
        demo_err("stream on failed, ret = %d", ret);
        return ret;
    }

    for (i = 0; i < deviceFormat->memDesc.memCount; i++) {
        V4L2DequeueBuffer(fd, &index, deviceFormat);
        if (cb) {
            demo_debug("[wwx1101856]: index = %d, deviceFormat->memDesc.memoryList[%d] = %p", index, i, &deviceFormat->memDesc.memoryList[i]);
            cb(index, &deviceFormat->memDesc.memoryList[i]);
        }
        V4L2QueueBuffer(fd, index, deviceFormat);
    }

    // stream off
    V4L2StreamOff(fd, deviceFormat->bufType);

    // free memory
    V4L2FreeBuf(deviceFormat);
    demo_debug("capture end");
}
